package de.lang.rollo.controller.logic;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.pi4j.concurrent.ExecutorServiceFactory;
import com.pi4j.io.gpio.GpioController;
import com.pi4j.io.gpio.GpioFactory;
import com.pi4j.io.gpio.GpioPinDigitalInput;
import com.pi4j.io.gpio.GpioPinDigitalOutput;
import com.pi4j.io.gpio.PinMode;
import com.pi4j.io.gpio.PinPullResistance;
import com.pi4j.io.gpio.PinState;
import com.pi4j.io.gpio.RaspiPin;
import com.pi4j.io.gpio.event.GpioPinDigitalStateChangeEvent;
import com.pi4j.io.gpio.event.GpioPinListenerDigital;
import com.yammer.dropwizard.config.Configuration;

import de.lang.rollo.controller.configurations.GpioConfiguration;
import de.lang.rollo.controller.enums.EventType;
import de.lang.rollo.controller.enums.MotorState;
import de.lang.rollo.controller.enums.SchalterType;
import de.lang.rollo.controller.interfaces.MotorCallback;
import de.lang.rollo.controller.managed.MongoManaged;
import de.lang.rollo.controller.model.RolloMotor;
import de.lang.rollo.controller.model.Schalter;
import de.lang.rollo.controller.model.daos.MotorDAO;
import de.lang.rollo.controller.model.daos.SchalterDAO;
import de.lang.rollo.controller.ressources.RolloAccessRessource;

public class GPIOFacade {
	private static Logger logger = LoggerFactory.getLogger(GPIOFacade.class);

	ArrayList<Schalter> schalter;
	HashMap<String, RolloMotor> motoren;
	ArrayList<RolloMotor> mot;

	GpioConfiguration config;
	ExecutorService executor;
	GpioController gpio;
	MotorDAO motorDao;
	SchalterDAO schalterDao;

	boolean runningOnPi;

	long minPressDiff;

	public GPIOFacade(List<Schalter> list, long minPressDiff, boolean runningOnPi) {
		logger.info("Initializing GPIOFacade");
		this.runningOnPi = runningOnPi;
		motorDao = new MotorDAO();
		schalterDao = new SchalterDAO();
		updateFacade2(list, minPressDiff);

	}

	private void updateFacade2(List<Schalter> list, long minPressDiff) {
		logger.info("Reinitializing GPIOFacade");
		schalter = new ArrayList<Schalter>(schalterDao.getAllSchalter());
		this.minPressDiff = minPressDiff;
		// schalter = new ArrayList<Schalter>();
		motoren = new HashMap<String, RolloMotor>();
		// mot = new ArrayList<RolloMotor>(motorDao.getAllMotoren());
		executor = Executors.newFixedThreadPool(list.size() / 2);
		if (runningOnPi)
			gpio = GpioFactory.getInstance();
		for (Schalter s : list) {
			schalter.add(s);

			if (!motoren.containsKey(s.getMotor_id())) {
				if (runningOnPi)
					s.getMotor().initGPIOPins(gpio);
				s.getMotor().setRunnable(new MotorRunner(s.getMotor()));
				s.getMotor().setSemaphore(new Semaphore(1));
				if (s.getType() == SchalterType.DOWN)
					logger.info("Registering " + s.getMotor().getName()
							+ "/ Motor Pin-Down: "
							+ s.getMotor().getPinDown().getAddress() + " with "
							+ s.getName() + " / Schalter Pin-Down"
							+ s.getPin().getAddress());
				else
					logger.info("Registering " + s.getMotor().getName()
							+ "/ MotorPin-Up: "
							+ s.getMotor().getPinUp().getAddress() + " with "
							+ s.getName() + " / Schalter Pin-Up"
							+ s.getPin().getAddress());

				motoren.put(s.getMotor_id(), s.getMotor());
			}
			if (runningOnPi) {
				GpioPinDigitalInput myButton = gpio.provisionDigitalInputPin(
						s.getPin(), PinPullResistance.PULL_UP);
				myButton.addListener(new SwitchEventListener(s));
			}
		}
	}

	public void doSomeShit() {
		System.out.println("--> GPIO state should be: ON");
		if (config.getRunningOnPi()) {
			// pin.toggle();
		}
	}

	public void blink() {
		if (config.getRunningOnPi()) {
			motoren.get(0).getPinUpOut().pulse(1000, PinState.LOW, false);
		}

	}

	static enum State {
		ON, OFF
	}

	public void driveDown(String motorId) {
		driveDown(motoren.get(motorId));
	}

	public void driveUp(String motorId) {
		driveUp(motoren.get(motorId));
	}

	public void driveDown(final RolloMotor m) {
		(new Thread() {
			public void run() {
				if (runningOnPi)
					try {
						m.getSemaphore().acquire();

						if (m.getPinUpOut().isLow()) {
							m.getPinUpOut().high();
							Thread.sleep(500);
						}
						m.getPinDownOut().pulse(m.getRunTimeDown(),
								PinState.LOW, false);
						Thread.sleep(500);
						m.getSemaphore().release();
					} catch (InterruptedException e) {
						logger.error("Semaphore ERROR with" + m.getName());
						e.printStackTrace();
					}

			}
		}).start();

	}

	public void driveUp(final RolloMotor m) {
		(new Thread() {
			public void run() {
				if (runningOnPi)
					try {
						m.getSemaphore().acquire();

						if (m.getPinDownOut().isLow()) {
							m.getPinDownOut().high();
							Thread.sleep(500);
						}
						m.getPinUpOut().pulse(m.getRunTimeUp(), PinState.LOW,
								false);
						Thread.sleep(500);
						m.getSemaphore().release();
					} catch (InterruptedException e) {
						logger.error("Semaphore ERROR with" + m.getName());
						e.printStackTrace();
					}

			}
		}).start();

	}

	public void stop(final RolloMotor m) {
		(new Thread() {
			public void run() {
				if (runningOnPi)
					try {
						m.getSemaphore().acquire();
						m.getPinUpOut().high();
						m.getPinDownOut().high();
						m.getSemaphore().release();
					} catch (InterruptedException e) {
						logger.error("Semaphore ERROR with" + m.getName());
						e.printStackTrace();
					}

			}
		}).start();
	}

	public void allUp() {
		logger.info("Alle Rollos hoch!");
		for (RolloMotor m : motoren.values()) {
			driveUp(m);
		}
	}

	public void allDown() {
		logger.info("Alle Rollos runter!");
		for (RolloMotor m : motoren.values()) {
			driveDown(m);
		}
	}

	public void allStop() {
		logger.info("Alle Rollos stopp!");
		for (RolloMotor m : motoren.values()) {
			stop(m);
		}
	}

	public MotorState getMotorState(RolloMotor m) {
		MotorState state = null;
		try {
			m.getSemaphore().acquire();

			if (m.getPinUpOut().isLow())
				state = MotorState.GOING_UP;
			else if (m.getPinDownOut().isLow())
				state = MotorState.GOING_DOWN;
			else
				state = MotorState.WAITING;

			m.getSemaphore().release();
		} catch (InterruptedException e) {
			logger.error("Semaphore ERROR with" + m.getName());
			e.printStackTrace();
		}
		return state;
	}

	/*
	 * Class for Manual Switch Event
	 */
	class SwitchEventListener implements GpioPinListenerDigital, MotorCallback {
		Schalter s;
		long lastPressUp = 0;
		long lastPressDown = 0;

		public SwitchEventListener(Schalter s) {
			this.s = s;
		}

		public void handleGpioPinDigitalStateChangeEvent(
				GpioPinDigitalStateChangeEvent event) {
			if (event.getState() == PinState.LOW) {
				System.out.println(" --> New Schalter-Event: " + s.getName()
						+ "/" + s.getPin().getAddress() + " = "
						+ event.getState());
				long currentTime = System.currentTimeMillis();
				switch (s.getType()) {
				case UP:
					if (currentTime - lastPressUp < minPressDiff) {
						logger.info("Last Press: "
								+ (currentTime - lastPressUp));
						allUp();
						lastPressUp = currentTime;
					} else {
						lastPressUp = currentTime;
						if (getMotorState(s.getMotor()) != MotorState.WAITING) {
							stop(s.getMotor());
						} else {
							driveUp(s.getMotor());
						}
					}
					break;
				case DOWN:
					if (currentTime - lastPressDown < minPressDiff) {
						logger.info("Last Press: "
								+ (currentTime - lastPressDown));
						allDown();
						lastPressDown = currentTime;
					} else {
						lastPressDown = currentTime;
						if (getMotorState(s.getMotor()) != MotorState.WAITING) {
							stop(s.getMotor());
						} else {
							driveDown(s.getMotor());
						}
					}
				}
			}
		}

		public void callback(MotorState state) {
			switch (state) {
			case DOWN:
				break;
			case GOING_DOWN:
				break;
			case GOING_UP:
				break;
			case UP:

			}

		}

	}

	public void shutdown() {
		if (runningOnPi)
			gpio.shutdown();

	}

}
